﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Sharkbite.Irc;
using System.Data.Metadata.Edm;
using System.Data.EntityClient;
using System.IO;
using System.Configuration;
using System.Data.SqlClient;
using System.Threading;

namespace FredBot
{
    class Basic
    {
        private Connection connection;
        private const string EntityConnectString = "Name = FredDataContext";
        private string TFEntityConnectionString;
        private HashSet<string> NicksInChannel;
        private bool isQuitting;
        private QuakenetWhoisParser qnParser;
        private Dictionary<string, WhoisActionItem> PendingWhoisActions;

        private static string GetConnectionStringByName(string name)
        {
            // Assume failure.
            string returnValue = null;

            // Look for the name in the connectionStrings section.
            ConnectionStringSettings settings =
                ConfigurationManager.ConnectionStrings[name];

            // If found, return the connection string.
            if (settings != null)
                returnValue = settings.ConnectionString;

            return returnValue;
        }
        /// <summary>
        /// Create a bot and register its handlers.
        /// </summary>
        public Basic()
        {
            CreateConnection();
            NicksInChannel = new HashSet<string>();
            isQuitting = false;
            qnParser = new QuakenetWhoisParser();
            PendingWhoisActions = new Dictionary<string, WhoisActionItem>();
            TFEntityConnectionString = GetConnectionStringByName("TFDataEntities");

            //OnRegister tells us that we have successfully established a connection with
            //the server. Once this is established we can join channels, check for people
            //online, or whatever.
            connection.Listener.OnRegistered += new RegisteredEventHandler(OnRegistered);
            //Listen for any messages sent to the channel
            connection.Listener.OnPublic += new PublicMessageEventHandler(OnPublic);

            //Listen for bot commands sent as private messages
            connection.Listener.OnPrivate += new PrivateMessageEventHandler(OnPrivate);
            //Listen for notification that an error has ocurred
            connection.Listener.OnError += new ErrorMessageEventHandler(OnError);

            //Listen for notification that we are no longer connected.
            connection.Listener.OnDisconnected += new DisconnectedEventHandler(OnDisconnected);

            connection.Listener.OnKick += new KickEventHandler(OnKick);
            connection.Listener.OnJoin += new JoinEventHandler(OnJoin);
            connection.Listener.OnQuit += new QuitEventHandler(OnQuit);
            connection.Listener.OnNames += new NamesEventHandler(OnNames);
            connection.Listener.OnNick += new NickEventHandler(OnNick);
            connection.Listener.OnPart += new PartEventHandler(OnPart);
            connection.Listener.OnWhois += new WhoisEventHandler(OnWhois);
        }

        void OnWhois(WhoisInfo whoisInfo)
        {
            if (PendingWhoisActions.ContainsKey(whoisInfo.User.Nick))
            {
                var myWhoisAction = PendingWhoisActions[whoisInfo.User.Nick];
                myWhoisAction.WhoisActionData.Add("authname", qnParser.IrcAuthNames[whoisInfo.User.Nick]);
                myWhoisAction.WhoisAction(myWhoisAction.WhoisActionData);
                PendingWhoisActions.Remove(whoisInfo.User.Nick);
                qnParser.IrcAuthNames.Remove(whoisInfo.User.Nick);
            }
        }

        private void OnQuit(UserInfo user, string reason)
        {
            using (EntityConnection ec = new EntityConnection(TFEntityConnectionString))
            {
                using (TFDataEntities context = new TFDataEntities(ec))
                {

                    var seenQry = from ent in context.LastSeenEntities
                                  where ent.Nick.Equals(user.Nick)
                                  select ent;

                    LastSeenEntity lastSeen = seenQry.FirstOrDefault();
                    if (lastSeen == null)
                    {
                        lastSeen = new LastSeenEntity();
                        lastSeen.Nick = user.Nick;
                        context.LastSeenEntities.AddObject(lastSeen);
                    }
                    lastSeen.LastSeen = DateTime.Now;
                    context.SaveChanges();
                }
            }
            NicksInChannel.Remove(user.Nick);
        }

        private void CreateConnection()
        {
            //The hostname of the IRC server
            string server = "irc.quakenet.org";

            //The bot's nick on IRC
#if DEBUG
            string nick = "FredBotD";
#else
            string nick = "FredBot";
#endif


            //Fire up the Ident server for those IRC networks
            //silly enough to use it.
            Identd.Start(nick);

            //A ConnectionArgs contains all the info we need to establish
            //our connection with the IRC server and register our bot.
            //This line uses the simplfied contructor and the default values.
            //With this constructor the Nick, Real Name, and User name are
            //all set to the same value. It will use the default port of 6667 and no server
            //password.
            ConnectionArgs cargs = new ConnectionArgs(nick, server);
            //When creating a Connection two additional protocols may be
            //enabled: CTCP and DCC. In this example we will disable them
            //both.
            connection = new Connection(cargs, false, false);

            //NOTE
            //We could have created multiple Connections to different IRC servers
            //and each would process messages simultaneously and independently.
            //There is no fixed limit on how many Connection can be opened at one time but
            //it is important to realize that each runs in its own Thread. Also,separate event
            //handlers are required for each connection, i.e. the
            //same OnRegistered () handler cannot be used for different connection
            //instances.
        }

        public void Start()
        {
            //Notice that by having the actual connect call here
            //the constructor can add the necessary listeners before
            //the connection process begins. If listeners are added
            //after connecting they may miss certain events. the OnRegistered()
            //event will certainly be missed.

            try
            {
                //Calling Connect() will cause the Connection object to open a
                //socket to the IRC server and to spawn its own thread. In this
                //separate thread it will listen for messages and send them to the
                //Listener for processing.
                connection.Connect();
                connection.AddParser(qnParser);

                Console.WriteLine("FredBot connected.");
                //The main thread ends here but the Connection's thread is still alive.
                //We are now in a passive mode waiting for events to arrive.
                connection.Sender.Names("#clan-slp");
            }
            catch (Exception e)
            {
                Console.WriteLine("Error during connection process.");
                Console.WriteLine(e);
                Identd.Stop();
            }
        }

        public void Stop()
        {
            if (connection.Connected)
            {
                isQuitting = true;
                connection.Disconnect("Leaving");
            }
        }

        public void OnNames(string channel, string[] nicks, bool last)
        {
            if (!last)
            {
                NicksInChannel.Clear();
                foreach (string nick in nicks)
                {
                    if (nick.StartsWith("@") || nick.StartsWith("+"))
                    {
                        NicksInChannel.Add(nick.Substring(1));
                    }
                    else
                    {
                        NicksInChannel.Add(nick);
                    }
                }
            }
        }

        public void OnNick(UserInfo user, string newNick)
        {
            NicksInChannel.Remove(user.Nick);
            NicksInChannel.Add(newNick);
        }

        public void OnJoin(UserInfo user, string channel)
        {
            if (!user.Nick.Equals(connection.ConnectionData.Nick))
            {
                NicksInChannel.Add(user.Nick);
                using (EntityConnection ec = new EntityConnection(TFEntityConnectionString))
                {
                    using (TFDataEntities context = new TFDataEntities(ec))
                    {
                        var tellQry = from tell in context.TellEntities
                                      where tell.Tellee.Equals(user.Nick)
                                      select tell;

                        List<TellEntity> tells = tellQry.ToList();
                        foreach (TellEntity t in tells)
                        {
                            connection.Sender.PublicMessage(channel, string.Format("{0}, {1} told me to tell you: {2}", user.Nick, t.Teller, t.Message));
                            context.TellEntities.DeleteObject(t);
                        }
                        context.SaveChanges();
                    }
                }
            }
        }

        public void OnKick(UserInfo user, string channel, string kickee, string reason)
        {
            if (kickee.Equals(connection.ConnectionData.Nick))
            {
                connection.Sender.Join(channel);
            }
            else
            {
                NicksInChannel.Remove(kickee);
            }
        }

        public void OnPart(UserInfo user, string channel, string reason)
        {
            NicksInChannel.Remove(user.Nick);
        }

        public void OnRegistered()
        {
            //We have to catch errors in our delegates because Thresher purposefully
            //does not handle them for us. Exceptions will cause the library to exit if they are not
            //caught.
            try
            {
                //Don't need this anymore in this example but this can be left running
                //if you want.
                Identd.Stop();

                //The connection is ready so lets join a channel.
                //We can join any number of channels simultaneously but
                //one will do for now.
                //All commands are sent to IRC using the Sender object
                //from the Connection.
                connection.Sender.Join("#clan-slp");
            }
            catch (Exception e)
            {
                Console.WriteLine("Error in OnRegistered(): " + e);
            }
        }

        public void OnPublic(UserInfo user, string channel, string message)
        {
            if (message.StartsWith("!"))
            {
                if (message.Equals("!"))
                {
                    connection.Sender.PublicMessage(channel, string.Format("You idiot, {0}, you have to actually tell me what you want to do.", user.Nick));
                }
                else
                {
                    ParseChannelCommand(user, channel, message.Substring(1));
                }
            }
            else
            {
                if (message.Contains(connection.ConnectionData.Nick))
                {
                    connection.Sender.PublicMessage(channel, string.Format("Who the fuck do you think you are, {0}, talking to me like that?", user.Nick));
                }
            }
        }

        private void ParseChannelCommand(UserInfo user, string channel, string message)
        {
            List<string> args = message.Split(new Char[] { ' ' }, 2).ToList();
            switch (args.First())
            {
                case "whoareyou":
                    connection.Sender.PublicMessage(channel, user.Nick + ": I am a channel bot written by FredFriendship because he was bored.  My primary purpose is to be a douchebag.  Idiot.");
                    break;
                case "seen":
                    SeenCmd(user, channel, args);
                    break;
                case "tell":
                    TellCmd(user, channel, args);
                    break;
                case "setsteamid":
                    SetSteamIdCmd(user, channel, args);
                    break;
                case "getsteamid":
                    GetSteamIdCmd(user, channel, args);
                    break;
                default:
                    connection.Sender.PublicMessage(channel, "Wtf are you trying to tell me, " + user.Nick + "?");
                    break;
            }
        }

        private void SetSteamIdCmd(UserInfo user, string channel, List<string> args)
        {
            if (args.Count < 2 || string.IsNullOrEmpty(args.ElementAt(1)))
            {
                connection.Sender.PublicMessage(channel, string.Format("Idiot, {0}, you're supposed to give me your steam id", user.Nick));
                return;
            }

            Dictionary<string, string> myArgs = new Dictionary<string, string>();
            myArgs.Add("steamId", args.ElementAt(1));

            Action<Dictionary<string, string>> setSteamAction = delegate(Dictionary<string, string> arguments)
            {
                IrcUsers ircuser = new IrcUsers();
                ircuser.IrcAuthName = arguments["authname"];
                ircuser.SteamID = arguments["steamId"];
                using (EntityConnection ec = new EntityConnection(TFEntityConnectionString))
                {
                    using (TFDataEntities context = new TFDataEntities(ec))
                    {
                        var existsQry = from u in context.IrcUsers
                                        where u.IrcAuthName.Equals(ircuser.IrcAuthName)
                                        select u;

                        IrcUsers theUser = existsQry.FirstOrDefault();
                        if (theUser == null)
                        {
                            context.IrcUsers.AddObject(ircuser);
                            context.SaveChanges();
                        }
                        else
                        {
                            theUser.SteamID = ircuser.SteamID;
                            context.IrcUsers.ApplyCurrentValues(theUser);
                        }
                    }
                }
            };

            PendingWhoisActions.Add(user.Nick, new WhoisActionItem(setSteamAction, myArgs));

            connection.Sender.Whois(user.Nick);
        }

        private void GetSteamIdCmd(UserInfo user, string channel, List<string> args)
        {
            string nick = user.Nick;
            if (args.Count == 2)
            {
                nick = args.ElementAt(1);
            }

            Dictionary<string, string> myArgs = new Dictionary<string, string>();
            myArgs.Add("nick", nick);

            Action<Dictionary<string, string>> getSteamAction = delegate(Dictionary<string, string> arguments)
            {
                using (EntityConnection ec = new EntityConnection(TFEntityConnectionString))
                {
                    using (TFDataEntities context = new TFDataEntities(ec))
                    {
                        string authname = arguments["authname"];
                        var userQry = from u in context.IrcUsers
                                      where u.IrcAuthName.Equals(authname)
                                      select u;

                        IrcUsers theUser = userQry.FirstOrDefault();
                        if (theUser == null)
                        {
                            connection.Sender.PublicMessage(channel, string.Format("{0} hasn't listed a Steam Id yet, idiot", nick));
                        }
                        else
                        {
                            connection.Sender.PublicMessage(channel, string.Format("{0}'s Steam Id is: {1}", nick, theUser.SteamID));
                        }
                    }
                }
            };

            PendingWhoisActions.Add(nick, new WhoisActionItem(getSteamAction, myArgs));

            connection.Sender.Whois(nick);
        }

        class IrcUserComparer : IEqualityComparer<IrcUsers>
        {
            public bool Equals(IrcUsers u1, IrcUsers u2)
            {
                if (u1.IrcAuthName.Equals(u2.IrcAuthName))
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }

            #region IEqualityComparer<IrcUsers> Members


            public int GetHashCode(IrcUsers obj)
            {
                return obj.IrcAuthName.GetHashCode();
            }

            #endregion
        }

        private void TellCmd(UserInfo user, string channel, List<string> args)
        {
            if (args.Count < 2 || string.IsNullOrEmpty(args.ElementAt(1)))
            {
                connection.Sender.PublicMessage(channel, string.Format("Idiot, {0}, you're supposed to tell me someone's name and a message to give them.", user.Nick));
                return;
            }

            List<string> nickAndMessage = args.ElementAt(1).Split(new Char[] { ' ' }, 2).ToList();
            if (NicksInChannel.Contains(nickAndMessage.ElementAt(0)))
            {
                connection.Sender.PublicMessage(channel, string.Format("{0} is right here in the channel you moron {1}, why don't you tell them yourself?", nickAndMessage.ElementAt(0), user.Nick));
                return;
            }

            TellEntity tell = new TellEntity();
            tell.Teller = user.Nick;
            tell.Tellee = nickAndMessage.ElementAt(0);
            tell.Message = nickAndMessage.ElementAt(1);
            using (EntityConnection ec = new EntityConnection(TFEntityConnectionString))
            {
                using (TFDataEntities context = new TFDataEntities(ec))
                {
                    context.TellEntities.AddObject(tell);
                    context.SaveChanges();
                }
            }
        }

        private void SeenCmd(UserInfo user, string channel, List<string> args)
        {
            if (args.Count < 2 || string.IsNullOrEmpty(args.ElementAt(1)))
            {
                connection.Sender.PublicMessage(channel, string.Format("You idiot, {0}, you're supposed to give me somebody's name with that.", user.Nick));
                return;
            }

            string nick = args.ElementAt(1);
            if (NicksInChannel.Contains(nick))
            {
                connection.Sender.PublicMessage(channel, string.Format("{0} is right here in the channel you moron {1}, of course I've seen them.", nick, user.Nick));
                return;
            }
            LastSeenEntity lastSeen;
            using (EntityConnection ec = new EntityConnection(TFEntityConnectionString))
            {
                using (TFDataEntities context = new TFDataEntities(ec))
                {

                    var seenQry = from ent in context.LastSeenEntities
                                  where ent.Nick.Equals(nick)
                                  select ent;

                    lastSeen = seenQry.FirstOrDefault();
                }
            }
            if (lastSeen != null)
            {
                connection.Sender.PublicMessage(channel, string.Format("{0} was last seen on {1}", nick, lastSeen.LastSeen.ToString()));
            }
            else
            {
                connection.Sender.PublicMessage(channel, string.Format("You idiot, {0}, {1} has never been seen in here.", user.Nick, nick));
            }
        }

        public void OnPrivate(UserInfo user, string message)
        {
            //Quit IRC if someone sends us a 'die' message
            if (message == "die")
            {
                connection.Disconnect("Bye");
            }
        }

        public void OnError(ReplyCode code, string message)
        {
            //All anticipated errors have a numeric code. The custom Thresher ones start at 1000 and
            //can be found in the ErrorCodes class. All the others are determined by the IRC spec
            //and can be found in RFC2812Codes.
            Console.WriteLine("An error of type " + code + " due to " + message + " has occurred.");
            string path = "errors.txt";
            File.AppendAllText(path, "An error of type " + code + " due to " + message + " has occurred." + Environment.NewLine);
        }

        public void OnDisconnected()
        {
            //If this disconnection was involutary then you should have received an error
            //message ( from OnError() ) before this was called.
            Console.WriteLine("Connection to the server has been closed.");
            if (!isQuitting)
            {
                Thread.Sleep(30000);
                Restart();
            }
        }

        public void Restart()
        {
            CreateConnection();
            Start();
        }
    }
}
